#  /**
#  * Copyright (c) 2022 Beijing Jiaotong University
#  * PhotLab is licensed under [Open Source License].
#  * You can use this software according to the terms and conditions of the [Open Source License].
#  * You may obtain a copy of [Open Source License] at: [https://open.source.license/]
#  *
#  * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
#  * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
#  * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#  *
#  * See the [Open Source License] for more details.
#  */
#  /**
#  * Author: Zheng Wang
#  * Created: Jul. 1, 2022
#  * Supported by: National Key Research and Development Program of China
#  */
import logging

from phot_engine.utils.unit_conversion import UnitConversion
from phot_engine.globals import VerificationError
from enum import Enum


class IntervalStatusEnum(Enum):
    """
        Description: IntervalStatusEnum class is used to describe interval status
        like:[-1,1],(-1,1),[-1,1),(-1,1]
    """
    LEFT_OPEN_RIGHT_CLOSED = 1
    RIGHT_OPEN_LEFT_CLOSED = 2
    BOTH_CLOSED = 3
    BOTH_OPEN = 4


class Parameter(object):
    """
        Description: Parameter class is used to save one component's physical parameter
    """

    class ParameterTypeEnum(Enum):
        """
            Description: to describe parameter's type
        """
        NUMERICAL_TYPE = 1
        INTERVAL_TYPE = 2
        UNCERTAIN_TYPE = 3

    # parameter public check list
    __PARAMETER_PUBLIC_KEY_CHECK_LIST = ("pyPhotName", "dataType", "unit", "photUnit")
    # numerical parameter check list
    __NUMERICAL_PARAMETER_KEY_CHECK_LIST = ("value",)
    # interval parameter check list
    __INTERVAL_PARAMETER_KEY_CHECK_LIST = ("interval", "intervalStatus",)
    # unit conversion module
    __unit_conversion = UnitConversion()

    def __init__(self, source_data):
        """
        :param source_data: a python dict type data,used to initialize Parameter object
        """
        self.param_type = self.ParameterTypeEnum.UNCERTAIN_TYPE
        self.param_name = ""
        self.param_value = None
        self.data_type = "string"
        self.param_unit = ""
        self.param_phot_unit = ""
        self.param_interval = []
        self.param_interval_status = ""

        try:
            self.source_data = source_data
            if self.__check_source_data_validation():
                # when you pass the validation,then you finish the instantiation and unit conversion
                pass
            else:
                raise VerificationError("source data validation can not pass")
        except Exception as e:
            print(e)

    def __check_source_data_validation(self):
        """
            1.Check source data's validity
            2.Assign value to a parameter type
        :return:True or False
        """
        if self.source_data is not None:
            if isinstance(self.source_data, dict):
                # a parameter has two type. pure numerical and interval,each type has own validity checking logic
                if self.__check_source_data_public_key():
                    if "interval" in self.source_data.keys():
                        if self.__check_interval_parameter():
                            self.__assign_interval_value()
                            return True
                    else:
                        if self.__check_numerical_parameter():
                            self.__assign_numerical_value()
                            return True
            else:
                raise VerificationError(
                    "source_data should be python dict type,not a {}".format(type(self.source_data)))
        else:
            raise VerificationError("source_data should not None")

    def __check_key(self, key_list):
        """
            检查一个字典类型中的key,数出给定key_list中的个数并和key_list长度进行判定,长度一致返回True,否则报错
            Check all key in a dict type,
            Counting the number in the given key_list and determine the length of the key_list
            Returns True if the length is consistent, otherwise a VerificationError type error is reported
        :param key_list:
        :return:
        """
        to_check_key_list = list(self.source_data.keys())
        cnt = 0
        for checking_key in to_check_key_list:
            if checking_key in key_list:
                cnt += 1
        if len(key_list) == cnt:
            return True
        else:
            raise VerificationError("Please check source_data's key: {},you should have {}."
                                    .format(self.source_data, key_list))

    def __check_source_data_public_key(self):
        """
            Check some public key between pure numerical and interval
        :return:
        """
        return self.__check_key(key_list=self.__PARAMETER_PUBLIC_KEY_CHECK_LIST)

    def __check_interval_parameter(self):
        """
            Check interval type parameter's key
        :return:
        """
        return self.__check_key(key_list=self.__INTERVAL_PARAMETER_KEY_CHECK_LIST)

    def __check_numerical_parameter(self):
        """
            Check numerical type parameter's key
        :return:
        """
        return self.__check_key(key_list=self.__NUMERICAL_PARAMETER_KEY_CHECK_LIST)

    def __convert_value_data_type(self, data_type, value):
        """
            convert param_value from string type to number
        :param data_type: dataType in the source dict
        :param value: value or Interval end value in the source dict
        :return:
        """
        try:
            if isinstance(data_type, str):
                if "int" == data_type or "long" == data_type or "short" == data_type:
                    return int(value)
                if "double" == data_type or "float" == data_type:
                    return float(value)
                if "string" == data_type:
                    return value
                if "tuple" == data_type:
                    return tuple(value)
                return value
            else:
                raise VerificationError("data_type should be a str type,not a {} type".format(type(data_type)))
        except Exception as e:
            pass

    def __assign_public_value(self):
        """
            Assign same parameter in numerical and interval types
        :return:
        """
        self.param_name = self.source_data["pyPhotName"]
        self.data_type = self.source_data["dataType"]
        self.param_unit = self.source_data["unit"]
        self.param_phot_unit = self.source_data["photUnit"]

    def __convert_unit(self, value):
        """
        convert current parameter's unit
        :param value:
        :return:
        """

        if len(self.param_phot_unit) > 0 and len(self.param_unit) > 0 and self.param_unit != self.param_phot_unit:
            unit_type = self.__unit_conversion.evaluate_unit_type(old_unit_str=self.param_unit,
                                                                  new_unit_str=self.param_phot_unit)
            converted_value = self.__unit_conversion.convert(unit_type, value, self.param_unit, self.param_phot_unit)
            debug_msg = str(unit_type)+"value:"+str(value)+self.param_unit+'->'+self.param_phot_unit+str(converted_value)
            logging.debug(msg=debug_msg)
            return converted_value

        else:
            return value

    def __assign_numerical_value(self):
        """
            Assign numerical value
        :return:
        """
        self.param_type = self.ParameterTypeEnum.NUMERICAL_TYPE
        self.__assign_public_value()
        # This value doesn't using unit conversion module
        value = self.__convert_value_data_type(self.data_type, self.source_data["value"])
        self.param_value = self.__convert_unit(value)

    def __assign_interval_value(self):
        """
            Assign interval value
        :return:
        """
        self.param_type = self.ParameterTypeEnum.INTERVAL_TYPE
        self.__assign_public_value()
        self.param_interval = [self.__convert_unit(
                                   self.__convert_value_data_type(self.data_type, self.source_data["interval"][0])),
                               self.__convert_unit(
                                   self.__convert_value_data_type(self.data_type, self.source_data["interval"][1]))]

        self.param_interval_status = self.__judge_interval_status(self.source_data["intervalStatus"])

    def __judge_interval_status(self, interval_status):
        """
            judge interval status by a string type interval_status
        :param interval_status: interval status in VPI's interval description [right open/left open/open/closed]
        :return:IntervalStatusEnum type description
        """
        if "right open" == interval_status:
            return IntervalStatusEnum.RIGHT_OPEN_LEFT_CLOSED
        elif "left open" == interval_status:
            return IntervalStatusEnum.LEFT_OPEN_RIGHT_CLOSED
        elif "open" == interval_status:
            return IntervalStatusEnum.BOTH_OPEN
        elif "closed" == interval_status:
            return IntervalStatusEnum.BOTH_CLOSED
        else:
            raise VerificationError("interval_status should be right open/left open/open/closed,now is {}"
                                    .format(interval_status))

    def __repr__(self):
        if self.param_type == self.ParameterTypeEnum.NUMERICAL_TYPE:
            return ("param_name: {},param_value: {},param_unit: {},param_phot_unit:{}"
                    .format(self.param_name, self.param_value, self.param_unit, self.param_phot_unit))

        elif self.param_type == self.ParameterTypeEnum.INTERVAL_TYPE:

            return ("param_name: {},param_unit: {},param_phot_unit:{},param_interval:{},param_interval_status:{}"
                    .format(self.param_name,
                            self.param_unit,
                            self.param_phot_unit,
                            self.param_interval,
                            self.param_interval_status))
        else:

            return ("param_name: {},param_value: {},param_unit: {},param_phot_unit:{},param_interval:{},"
                    "param_interval_status:{} "
                    .format(self.param_name,
                            self.param_value,
                            self.param_unit,
                            self.param_phot_unit,
                            self.param_interval,
                            self.param_interval_status))


if __name__ == '__main__':
    test_parameter_dict_list = [{
        "pyPhotName": "底层运算库中用到的函数参数名",
        "dataType": "float",
        "value": "1.3e2",
        "unit": "m",
        "photUnit": "nm"
    }, {
        "pyPhotName": "底层运算库中用到的函数参数名",
        "dataType": "float",
        "interval": ["-10", "10"],
        # right open/left open/open/closed
        "intervalStatus": "right open",
        "unit": "db",
        "photUnit": "mw"
    }]

    for elem in test_parameter_dict_list:
        param = Parameter(elem)
        print(param)
